package org.anderground.app.storage;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import org.anderground.core.Logger;
import org.anderground.core.model.City;
import org.anderground.core.model.Country;
import org.anderground.core.model.IdBindingModelException;
import org.anderground.core.util.Coord;

import android.util.SparseArray;

/**
 * Intermediate data presentation level suitable for converting between XML and
 * SQL.
 * 
 * @author Eldar Abusalimov
 */
public class ConfigData {

	/**
	 * @author Eldar Abusalimov
	 */
	public class ConfigCity extends City {

		private static final long serialVersionUID = 4466093678782862754L;

		private final int id;
		private final URL url;

		/**
		 * @param name
		 * @param aliases
		 * @param country
		 * @param location
		 * @param url
		 */
		private ConfigCity(String name, List<String> aliases, Country country,
				Coord location, URL url) {
			super(name, aliases, country, location);
			if (url == null) {
				throw new IllegalArgumentException("url cannot be null");
			}
			this.url = url;
			SparseArray<ConfigCity> configDataCities = ConfigData.this.cities;
			id = configDataCities.size() + 1;
			configDataCities.append(id, this);
		}

		/**
		 * @return the id
		 */
		public long getId() {
			return id;
		}

		/**
		 * @return the url
		 */
		public URL getUrl() {
			return url;
		}

		@Override
		public String toString() {
			return id + " : " + getName() + " : " + url;
		}

	}

	/**
	 * TODO split SAX related methods to a separate class
	 * 
	 * @author Eldar Abusalimov
	 */
	public static class Builder {

		private ConfigData data;

		/**
		 * temporary structure class to store raw data about City element
		 * 
		 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
		 */
		private class CityStub {
			String name, countryCode;
			ArrayList<String> aliases = new ArrayList<String>();
			Coord location;
			URL url;
		}

		private String countryCode;
		private HashMap<String, Country> countriesMap = new HashMap<String, Country>();

		private List<CityStub> cityStubs = new LinkedList<CityStub>();
		private CityStub cityStub;

		/**
		 * 
		 */
		public Builder() {
			data = new ConfigData();
		}

		/**
		 * @return the data
		 * @throws IdBindingModelException
		 */
		public ConfigData build() throws IdBindingModelException {
			for (CityStub cityStub : cityStubs) {
				if (cityStub.url == null) {
					Logger.w("ignoring ConfigCity with null URL");
					continue;
				}
				Country country = countriesMap.get(cityStub.countryCode);
				if (country == null) {
					throw new IdBindingModelException("No country with code "
							+ cityStub.countryCode);
				}
				data.new ConfigCity(cityStub.name, cityStub.aliases, country,
						cityStub.location, cityStub.url);
			}

			data.countries.clear();
			data.countries.addAll(countriesMap.values());
			return data;
		}

		/**
		 * called when a new city element has occurred
		 * 
		 * @param countryCode
		 */
		public void startCity(String countryCode) {
			cityStub = new CityStub();
			cityStubs.add(cityStub);
			cityStub.countryCode = countryCode;
		}

		/**
		 * called when the city's name retrieved
		 * 
		 * @param cityName
		 */
		public void setCityName(String cityName) {
			cityStub.name = cityName;
		}

		/**
		 * adds a new city name alias
		 * 
		 * @param alias
		 */
		public void addCityAlias(String alias) {
			cityStub.aliases.add(alias);
		}

		/**
		 * adds a new city name alias
		 * 
		 * @param latitude
		 * @param longitude
		 */
		public void setCityLocation(int latitude, int longitude) {
			cityStub.location = new Coord(latitude, longitude);
		}

		/**
		 * called when the country's name retrieved
		 * 
		 * @param countryName
		 */
		public void setCountryName(String countryName) {
			Country country = new Country(countryCode, countryName);
			countriesMap.put(country.getCode(), country);
		}

		/**
		 * sets the country code
		 * 
		 * @param code
		 */
		public void startCountry(String code) {
			countryCode = code;
		}

		/**
		 * called when the url has been retrieved
		 * 
		 * @param url
		 */
		public void setUrl(URL url) {
			cityStub.url = url;
		}

	}

	private final SparseArray<ConfigCity> cities = new SparseArray<ConfigCity>();
	private final List<Country> countries = new ArrayList<Country>();
	private final List<Country> unmodifiableCountriesList = Collections
			.unmodifiableList(countries);

	/**
	 * 
	 */
	public ConfigData() {
	}

	/**
	 * @param key
	 * @return
	 * @see android.util.SparseArray#get(int)
	 */
	public ConfigCity getCity(int key) {
		return cities.get(key);
	}

	/**
	 * @param key
	 * @param valueIfKeyNotFound
	 * @return
	 * @see android.util.SparseArray#get(int, java.lang.Object)
	 */
	public ConfigCity getCity(int key, ConfigCity valueIfKeyNotFound) {
		return cities.get(key, valueIfKeyNotFound);
	}

	/**
	 * @return the countries
	 */
	public List<Country> getCountries() {
		return unmodifiableCountriesList;
	}

	/**
	 * @param key
	 * @return
	 * @see android.util.SparseArray#indexOfKey(int)
	 */
	public int indexOfKey(int key) {
		return cities.indexOfKey(key);
	}

	/**
	 * @param value
	 * @return
	 * @see android.util.SparseArray#indexOfValue(java.lang.Object)
	 */
	public int indexOfValue(ConfigCity value) {
		return cities.indexOfValue(value);
	}

	/**
	 * @param index
	 * @return
	 * @see android.util.SparseArray#keyAt(int)
	 */
	public int keyAt(int index) {
		return cities.keyAt(index);
	}

	/**
	 * @return
	 * @see android.util.SparseArray#size()
	 */
	public int size() {
		return cities.size();
	}

	/**
	 * @param index
	 * @return
	 * @see android.util.SparseArray#valueAt(int)
	 */
	public ConfigCity valueAt(int index) {
		return cities.valueAt(index);
	}

	public void dump() {
		Logger.d("ConfigData DUMP START >>>>>>");
		Logger.d("  countries:");
		for (Country country : countries) {
			Logger.d("    " + country);
		}
		Logger.d("  cities:");
		final int size = size();
		for (int i = 0; i < size; i++) {
			ConfigCity city = valueAt(i);
			Logger.d("    " + city);
		}
		Logger.d("<<<<<< DUMP END");
	}

}
